<?php

namespace fwkit;

class Utils
{
    /**
     * Executes a callback function.
     *
     * @param callback $callback Callback function
     * @param array $params Function parameters
     * @return mixed Function results
     * @throws \Exception
     */
    public static function execute(callable $callback, ?array $params = [])
    {
        $params = $params ?? [];
        if (is_callable($callback)) {
            return is_array($callback) ?
                self::invokeMethod($callback, $params) :
                self::callFunction($callback, $params);
        } else {
            throw new \Exception('Invalid callback specified.');
        }
    }

    /**
     * Calls a function.
     *
     * @param string $func Name of function to call
     * @param array $params Function parameters
     * @return mixed Function results
     */
    public static function callFunction($func, ?array $params = [])
    {
        $params = $params ? array_values($params) : [];
        return $func(...$params);
    }

    /**
     * Invokes a method.
     *
     * @param mixed $func Class method
     * @param array $params Class method parameters
     * @return mixed Function results
     */
    public static function invokeMethod($func, ?array $params = [])
    {
        $params = $params ? array_values($params) : [];
        list($class, $method) = $func;
        return is_object($class) ?
            $class->{$method}(...$params) :
            $class::{$method}(...$params);
    }

    /**
     * Gets a new instance of a class.
     *
     * @param string|callable $class Class name or callback function to instantiate class
     * @param array $params Class initialization parameters
     * @return object Class instance
     */
    public static function newInstance($class, ?array $params = [])
    {
        $class = str_replace('.', '\\', $class);
        $params = $params ? array_values($params) : [];
        if (is_callable($class)) {
            return self::execute($class, $params);
        }

        return new $class(...$params);
    }

    public static function isList($arr): bool
    {
        if (!is_array($arr)) {
            return false;
        } elseif (empty($arr)) {
            return true;
        } else {
            $keys = array_keys($arr);
            return array_keys($keys) === $keys;
        }
    }

    public static function startsWith(string $haystack, $needle, bool $caseInsensitivity = false): bool
    {
        if (!$needle) return true;

        if (is_array($needle)) {
            foreach ($needle as $val) {
                if (self::startsWith($haystack, $val, $caseInsensitivity)) {
                    return true;
                }
            }

            return false;
        }

        return substr_compare($haystack, $needle, 0, strlen($needle), $caseInsensitivity) === 0;
    }

    public static function endsWith(string $haystack, $needle, bool $caseInsensitivity = false): bool
    {
        if (!$needle) return true;

        if (is_array($needle)) {
            foreach ($needle as $val) {
                if (self::endsWith($haystack, $val, $caseInsensitivity)) {
                    return true;
                }
            }

            return false;
        }

        $len = strlen($needle);
        return substr_compare($haystack, $needle, -$len, $len, $caseInsensitivity) === 0;
    }

    public static function element(array &$arr, $key, $defaultValue = null)
    {
        return array_key_exists($key, $arr) ? $arr[$key] : $defaultValue;
    }

    public static function popElement(array &$arr, $key, $defaultValue = null)
    {
        if (array_key_exists($key, $arr)) {
            $value = $arr[$key];
            unset($arr[$key]);
            return $value;
        }

        return $defaultValue;
    }

    public static function camelize(string $text, string $separator = '_')
    {
        $text = $separator . str_replace($separator, ' ', strtolower($text));
        return ltrim(str_replace(' ', '', ucwords($text)), $separator);
    }

    public static function uncamelize(string $text, string $separator = '_')
    {
        return strtolower(preg_replace('/([a-z])([A-Z])/', '$1' . $separator . '$2', $text));
    }

    public static function createNonceStr($length = 16) {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $str = '';
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }

        return $str;
    }

    public function isCoroutine(): bool
    {
        return \class_exists('\\Swoole\\Coroutine') && \Swoole\Coroutine::getuid() > 0;
    }
}
